% Definition of float context "class"

/context-add-absolute-positioned { % => Box Context
  dup /AbsolutePositioned get      % => Box Context AB
  2 index exch
  array-prepend                    % => Box Context AB'
  /AbsolutePositioned exch put     % => Box
  pop
} def

/context-add-fixed-positioned { % => Box Context
  dup /FixedPositioned get      % => Box Context AB
  2 index exch
  array-prepend                    % => Box Context AB'
  /FixedPositioned exch put     % => Box
  pop
} def

/context-add-float {               % => Float Context
  dup /Floats get                  % => Float Context Floats
  dup 0 get                        % => Float Context Floats Floats[0]
  3 index exch array-prepend       % => Float Context Floats Floats[0]'
  0 exch put                       % => Float Context 
  pop pop 
} def

/context-container-uid {           % => Context
  /ContainerUID get 0 get
} def

/context-create {                  % =>
  <<
    /ContainerUID [1]
    /AbsolutePositioned []
    /FixedPositioned []
    /Floats [[]]
    /Margin [0 0]
    /Viewport []
  >>
} def

% Find the minimal X at the given Y coordinate suitable for float placement
%
/context-float-left-x {            % => Y X Context
  3 copy dup 
  context-floats                   % => Y X Context Y X Context Floats
  context-float-left-x-rec         % => Y X Context X
  
% Clear the stack
  exch pop
  exch pop
  exch pop
} def

/context-float-left-x-rec {        % => Y X Context Floats
  dup length 0 gt {
    dup 0 get                      % => Y X Context Floats Float

    dup /float get-css-value
    /left eq {
    
% Check if this float contains given Y-coordinate
%
% Note that top margin coordinate is inclusive but 
% bottom margin coordinate is exclusive! The cause is following:
% - if we have several floats in one line, their top margin edge Y coordinates will be equal,
%   so we must use agreater or equal sign to avod placing all floats at one X coordinate
% - on the other side, if we place one float under the other, the top margin Y coordinate 
%   of bottom float will be equal to bottom margin Y coordinate of the top float and 
%   we should NOT offset tho bottom float in this case

      dup get-top-margin             % => Y X Context Floats Float FTM
      rounding-epsilon add
      5 index                        % => Y X Context Floats Float FTM Y
      ge                             % => Y X Context Floats Float FTM>=Y

      1 index get-bottom-margin      % => Y X Context Floats Float FTM>=Y FBM
      6 index                        % => Y X Context Floats Float FTM>=Y FBM Y
      lt                             % => Y X Context Floats Float FTM>=Y FBM<Y

      and {                          % => Y X Context Floats Float
        dup get-right-margin         % => Y X Context Floats Float FRM
        4 index max                  % => Y X Context Floats Float X'=MAX(FRM,X)
        exch pop                     % => Y X Context Floats X'
        4 3 roll pop                 % => Y Context Floats X'
        3 1 roll                     % => Y X' Context Floats
        array-pop-first
        context-float-left-x-rec     % => X
      } {
        pop
        array-pop-first
        context-float-left-x-rec
      } ifelse
    } {
      pop
      array-pop-first
      context-float-left-x-rec
    } ifelse
  } {
% no more floats
    pop pop exch pop
  } ifelse
} def                              % => X

% Calculates position of left floating box (taking into account the possibility 
% of "wrapping" float to next line in case we have not enough space at current level (Y coordinate)
%
% @param parent reference to a parent box
% @param width width of float being placed. Full width! so, extra horizontal space (padding, margins and borders) is added here too
% @param $y Starting Y-coordinate 
% @return X X coordinate of float upper-left corner
% @return Y Y coordinate of float upper-left corner
%
/context-float-left-xy {           % => Parent Width Y Context
% Prepare information about the float bottom corrdinates
  dup context-floats               % => Parent Width Y Context Floats
  make-sorted-bottom-y-list        % => Parent Width Y Context FloatBottoms

  context-float-left-xy-rec        % => Y X
} def                              % => Y X

/context-float-left-xy-rec {       % => Parent Width Y Context FloatBottoms
  4 index get-left
  3 index                          % => Parent Width Y Context FloatBottoms X Y
  exch                             % => Parent Width Y Context FloatBottoms Y X
  3 index
  context-float-left-x             % => Parent Width Y Context FloatBottoms X

% Check if current float will fit into the parent box
  dup 5 index add                  % => Parent Width Y Context FloatBottoms X FloatRight
  6 index get-right
  rounding-epsilon add
  le {                             % => Parent Width Y Context FloatBottoms X 
% will fit
    exch pop                       % => Parent Width Y Context X
    exch pop                       % => Parent Width Y X
    4 2 roll                       % => Y X Parent Width
    pop pop exch                   % => X Y
  } {
    pop                            % => Parent Width Y Context FloatBottoms
% Check if all floats have been already cleared
    dup length 0 eq {
% All floats are cleared; fall back to the leftmost X coordinate
      pop pop exch pop             % => Parent Y
      exch                         % => Y Parent
      get-left exch                % => X Y
    } {
% No, float does not fit at current level, let's try to 'clear' some previous floats
      dup 0 get                    % => Parent Width Y Context FloatBottoms Bottom0
      3 index min                  % => Parent Width Y Context FloatBottoms Y'
      4 3 roll pop                 % => Parent Width Context FloatBottoms Y'
      3 1 roll
      array-pop-first              % => Parent Width Y' Context FloatBottoms'
      context-float-left-xy-rec    % => X Y
    } ifelse
  } ifelse
} def                              % => X Y

% Find the minimal X at the given Y coordinate suitable for float placement
%
/context-float-right-x {           % => Y X Context
  3 copy dup
  context-floats                   % => Y X Context Y X Context Floats
  context-float-right-x-rec        % => Y X Context X
  
% Clear the stack
  exch pop
  exch pop
  exch pop
} def

/context-float-right-x-rec {       % => Y X Context Floats
  dup length 0 gt {
    dup 0 get                      % => Y X Context Floats Float

    dup /float get-css-value
    /right eq {

% Check if this float contains given Y-coordinate
%
% Note that top margin coordinate is inclusive but 
% bottom margin coordinate is exclusive! The cause is following:
% - if we have several floats in one line, their top margin edge Y coordinates will be equal,
%   so we must use agreater or equal sign to avod placing all floats at one X coordinate
% - on the other side, if we place one float under the other, the top margin Y coordinate 
%   of bottom float will be equal to bottom margin Y coordinate of the top float and 
%   we should NOT offset tho bottom float in this case

      dup get-top-margin             % => Y X Context Floats Float FTM
      rounding-epsilon add
      5 index                        % => Y X Context Floats Float FTM Y
      ge                             % => Y X Context Floats Float FTM>=Y

      1 index get-bottom-margin      % => Y X Context Floats Float FTM>=Y FBM
      6 index                        % => Y X Context Floats Float FTM>=Y FBM Y
      lt                             % => Y X Context Floats Float FTM>=Y FBM<Y

      and {                          % => Y X Context Floats Float
        dup get-left-margin          % => Y X Context Floats Float FRM
        4 index min                  % => Y X Context Floats Float X'=MAX(FRM,X)
        exch pop                     % => Y X Context Floats X'
        4 3 roll pop                 % => Y Context Floats X'
        3 1 roll                     % => Y X' Context Floats
        array-pop-first
        context-float-right-x-rec    % => X
      } {                            % => Y X Context Floats Float
        pop                          % => Y X Context Floats 
        array-pop-first
        context-float-right-x-rec    % => X
      } ifelse
    } {
      pop
      array-pop-first
      context-float-right-x-rec
    } ifelse
  } {
% no more floats
    pop pop exch pop
  } ifelse
} def                              % => X

% Calculates position of left floating box (taking into account the possibility 
% of "wrapping" float to next line in case we have not enough space at current level (Y coordinate)
%
% @param parent reference to a parent box
% @param width width of float being placed. Full width! so, extra horizontal space (padding, margins and borders) is added here too
% @param $y Starting Y-coordinate 
% @return X X coordinate of float upper-left corner
% @return Y Y coordinate of float upper-left corner
%
/context-float-right-xy {          % => Parent Width Y Context
% Prepare information about the float bottom corrdinates
  dup context-floats               % => Parent Width Y Context Floats
  make-sorted-bottom-y-list        % => Parent Width Y Context FloatBottoms

  context-float-right-xy-rec       % => X Y
} def                              % => X Y

/context-float-right-xy-rec {      % => Parent Width Y Context FloatBottoms
  4 index get-right
  3 index                          % => Parent Width Y Context FloatBottoms X Y
  exch
  3 index 
  context-float-right-x            % => Parent Width Y Context FloatBottoms X

% Check if current float will fit into the parent box
  dup                              % => Parent Width Y Context FloatBottoms X X
  6 index get-right               
  rounding-epsilon add             % => Parent Width Y Context FloatBottoms X X FRight
  le {                             % => Parent Width Y Context FloatBottoms X 
% will fit
    exch pop exch pop              % => Parent Width Y X
    4 2 roll                       % => Y X Parent Width
    pop pop exch                   % => X Y
  } {
    pop                            % => Parent Width Y Context FloatBottoms
% Check if all floats have been already cleared
    dup length 0 eq {
% All floats are cleared; fall back to the leftmost X coordinate
      pop pop exch pop             % => Parent Y
      exch                         % => Y Parent
      get-left exch                % => X Y
    } {
% No, float does not fit at current level, let's try to 'clear' some previous floats
      dup 0 get                    % => Parent Width Y Context FloatBottoms Bottom0
      3 index min                  % => Parent Width Y Context FloatBottoms Y'
      4 3 roll pop                 % => Parent Width Context FloatBottoms Y'
      3 1 roll 
      array-pop-first              % => Parent Width Y' Context FloatBottoms'
      context-float-left-xy-rec    % => X Y
    } ifelse
  } ifelse
} def                              % => X Y

/context-floats {                  % => Context
  /Floats get 0 get
} def

/context-get-absolute-positioned { % => Context
  /AbsolutePositioned get   
} def

/context-get-collapsed-margin {    % => Context
  /Margin get 0 get
} def

/context-get-fixed-positioned { % => Context
  /FixedPositioned get   
} def

/context-get-viewport {            % => Context
  /Viewport get 0 get
} def

/context-point-in-floats {         % => Y X Context
  /null                            % => Y X Context /null
  1 index context-floats           % => Y X Context /null Floats
  {                                % => Y X Context /null Float
    4 index
    4 index
    2 index
    box-generic-contains-point-margin {        % => Y X Context /null Float
      exch pop
      exit                         % => Y X Context Float
    } if
    pop
  } forall                         % => Y X Context Float
    
  exch pop
  exch pop
  exch pop
} def

/context-pop {                     % => Context
  dup context-pop-collapsed-margin
  dup context-pop-floats
  pop
} def

/context-pop-collapsed-margin {    % => Context
  dup /Margin get                  % => Context CMT
  array-pop-first                  % => Context CMT'
  /Margin exch put                 % => 
} def

/context-pop-container-uid {       % => Context
  dup /ContainerUID get
  array-pop-first
  /ContainerUID exch put
} def

/context-pop-floats {              % => Context
  dup /Floats get
  array-pop-first
  /Floats exch put
} def

/context-pop-viewport {            % => Context
  dup /Viewport get
  array-pop-first                  % => Context Viewports
  /Viewport exch put               % =>
} def

/context-push {                    % => Context
  0 1 index context-push-collapsed-margin
  dup context-push-floats
  pop
} def

/context-push-collapsed-margin {   % => Value Context
  dup /Margin get                  % => Value Context CMT
  2 index exch                     % => Value Content Value CMT
  array-append                     % => Value Context CMT'
  /Margin exch put                 % => Value
  pop
} def

/context-push-container-uid {      % => Uid Context
  dup /ContainerUID get            % => Uid Context UIDStack
  2 index exch array-append        % => Uid Context UIDStack'
  1 index exch
  /ContainerUID exch put
  pop pop
} def

/context-push-floats {             % => Context
  dup /Floats get
  [] exch array-append             % => Context Floats'
  /Floats exch                     % => Context /Floats Floats'
  put                              % => 
} def

/context-push-viewport {           % => Viewport Context
  dup /Viewport get                % => Viewport Context Viewports
  2 index exch array-append        % => Viewport Context Viewports'
  1 index exch /Viewport exch put  % => Viewport Context
  pop pop
} def

% helper utility
/make-sorted-bottom-y-list {       % => Boxes
  {
    get-bottom-margin
    exch array-prepend
  } exch
  [] exch
  reduce                           % => UnsortedBottomsYs

  { gt }                           % => UnsortedBottomsYs GtFun
  array-sort                       % => SortedBottomYs
} def

%%%%%%%%%%%%%%%%%%%%%

/empty-context {
  << /Floats [] /CollapsedMarginTop [0] >>
} def

/context-stack [ empty-context ] def

/push-context {
  empty-context
  context-stack
  array-append
  
  /context-stack exch def
} def

/pop-context {
  context-stack
  array-pop-first

  /context-stack exch def
} def

/context-current {
  context-stack 0 get
} def

/context-floats-bottom {           % => MaxValue
  { get-bottom-margin min } exch
  context-floats reduce
} def

/context-save-float {              % => Float
  context-current
  /Floats get

  array-append
  
  context-current exch
  /Floats exch
  put
} def

% Get the bottom edge coordinate of the bottommost float in 
% current formatting context
%
% @return /null in case of no floats exists in current context
% numeric coordinate value otherwise
% 
/context-float-bottom {            % =>
  context-floats
  dup length 0 gt {
    { get-bottom-margin min }
    exch
    dup 0 get get-bottom-margin
    exch
    reduce
  } {
    pop /null
  } ifelse
} def

% Get the right edge coordinate of the rightmost float in 
% current formatting context
%
% @return null in case of no floats exists in current context
% numeric coordinate value otherwise
% 
/context-float-right {             % =>
  context-floats
  dup length 0 gt {
    { get-right-margin min }
    exch
    dup 0 get get-right-margin
    exch
    reduce
  } {
    pop /null
  } ifelse
} def